Skip to content

fix(upload): enforce size/type limits and derive storage path server-side#4

Merged
ralyodio merged 1 commit into
profullstack:mainfrom
eltociear:fix/upload-route-auth-and-limits
Jul 7, 2026
Merged

fix(upload): enforce size/type limits and derive storage path server-side#4
ralyodio merged 1 commit into
profullstack:mainfrom
eltociear:fix/upload-route-auth-and-limits

Conversation

@eltociear

Copy link
Copy Markdown
Contributor

Fixes #3.

/api/upload uses the service-role Supabase client (bypasses storage RLS) with no size cap, no content-type allowlist, and a storage path taken straight from the request body. Anonymous upload is intentional (it's how a visitor attaches a logo/screenshot while submitting a new listing), so this doesn't add auth — it just closes the resource-abuse gaps:

  • File size capped at 5MB.
  • Content-type allowlisted to actual image types (png/jpeg/webp/gif/svg) instead of trusting the client's claimed file.type.
  • Storage path is now always derived server-side (Date.now()-crypto.randomUUID().ext) instead of accepted from the client, so a direct API call can't collide with/squat another upload's key.

Small, focused diff — only app/api/upload/route.ts changed.

…side

/api/upload uses the service-role Supabase client (bypasses storage RLS),
which src/lib/supabaseAdmin.ts's own doc comment says callers must gate with
their own authorization/limits before running. This route had none: no file
size cap, no content-type allowlist (the client-reported file.type was
trusted directly as contentType), and the storage path came straight from
the request body instead of being generated server-side.

Anonymous submission is intentional here (visitors upload a logo/screenshot
while submitting a new listing, see src/views/Submit.tsx), so this doesn't
add auth — but it does close off:
- unbounded storage/egress cost from arbitrarily large or numerous uploads
  (now capped at 5MB per file)
- uploading non-image content under an attacker-chosen content-type
  (now allowlisted to png/jpeg/webp/gif/svg)
- a caller picking their own storage key, which could collide with another
  upload's path (upsert: false would then reject the legitimate one) or be
  used to write outside the flat filename pattern the frontend generates
  (path is now always server-derived: timestamp + randomUUID + extension)
@ralyodio ralyodio merged commit 51c046e into profullstack:main Jul 7, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unauthenticated /api/upload has no size/type limits and trusts a client-supplied storage path

2 participants